/*
 * Author: yezi
 * E-mail: okye328@gmail.com
 * Create time: Jan 3, 2013 12:05:02 PM
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 */
package com.kanbox.linux.operation;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.kanbox.linux.GlobalConf;
import com.kanbox.linux.fs.FolderHash;
import com.kanbox.linux.fs.INode;
import com.kanbox.linux.fs.MountPointManager;
import com.kanbox.linux.util.FileUtil;

public class FileSynchronizer {

	private static Logger logger = LoggerFactory
			.getLogger(FileSynchronizer.class);

	private File[] files;

	private File dataRoot = new File(MountPointManager.getDataPath());

	public FileSynchronizer() {
		refreshFolderHashFiles();
	}

	public void syncALL() {
		// 1st, check all files from the hash folder
		Set<File> finished = new HashSet<>();
		Set<File> remaining = new HashSet<>();

		for (File file : files) {
			remaining.add(file);
		}

		while (remaining.size() > 0) {
			for (File file : remaining) {
				check(file);
				finished.add(file);
			}
			remaining.removeAll(finished);

			refreshFolderHashFiles();
			for (File file : files) {
				if (!finished.contains(file)) {
					remaining.add(file);
				}
			}
		}

		// 2nd, check the empty folder from local(if a FileList check, will
		// create the empty folder)
		checkAllDirs();
	}

	private void check(File file) {
		FolderHash fh = new FolderHash(file);
		String path = fh.getPath();
		File dataFile = new File(MountPointManager.remoteToLocalDataPath(path));
		FileList fl = new FileList();
		List<INode> inodes;
		try {
			inodes = fl.list(path);
			// By now , the fh(FolderHash) object will change, we can use
			// List<INode>
		} catch (FileNotFoundException e) {
			FileUtil.deleteAll(dataFile);
			file.delete();
			return;
		}

		final Set<File> downloadedSet = new HashSet<File>();

		int nThreads = inodes.size();
		Thread[] threads = new Thread[nThreads];
		for (int i = 0; i < inodes.size(); i++) {
			final INode iNode = inodes.get(i);
			if (!iNode.isFolder()) {
				threads[i] = new Thread(new Runnable() {
					@Override
					public void run() {
						try {
							File newFile = new FileDownloader(iNode).downLoad();
							downloadedSet.add(newFile);
						} catch (IOException e) {
							logger.error("Error when downloading file", e);
						}
					}
				});
				threads[i].start();
			}
		}

		for (Thread thread : threads) {
			if (thread != null) {
				try {
					thread.join();
				} catch (InterruptedException e) {
					logger.error("Download thread interrupted", e);
				}
			}
		}
		// // /////////////////////////////////////////////////////
		// for (INode inode : inodes) {
		// if (!inode.isFolder())
		// try {
		// File newFile = new FileDownloader(inode).downLoad();
		// downloadedSet.add(newFile);
		// } catch (IOException e) {
		// logger.error("Error when downloading file", e);
		// continue;
		// }
		// }
		// // ////////////////////////////////////////////////////
		File[] existFiles = dataFile.listFiles();
		for (File eF : existFiles) {
			if (eF.isFile() && !downloadedSet.contains(eF)) {
				eF.delete();
			}
		}
	}

	private List<File> getAllDirs() {
		LinkedList<File> res = new LinkedList<File>();
		LinkedList<File> todoList = new LinkedList<File>();
		todoList.add(dataRoot);
		while (todoList.size() != 0) {
			File todo = todoList.removeFirst();
			if (todo.isFile())
				continue;
			res.add(todo);
			File[] files = todo.listFiles();
			for (File file : files) {
				todoList.add(file);
			}
		}
		return res;
	}

	private void checkAllDirs() {
		List<File> files = getAllDirs();
		for (File file : files) {
			if (!file.exists()) {
				continue;
			}
			String path = MountPointManager.localToRemoteDataPath(file
					.getAbsolutePath());

			if (!FolderHash.isExists(path)) {
				FileUtil.deleteAll(file);
				continue;
			}
			List<INode> newINodes = null;
			try {
				newINodes = new FileList().listRecurse(path);
			} catch (FileNotFoundException e) {
				// Nothing happen
			}
			if (newINodes != null) {
				int nThreads = newINodes.size();
				Thread[] threads = new Thread[nThreads];
				for (int i = 0; i < newINodes.size(); i++) {
					INode iNode = newINodes.get(i);
					if (!iNode.isFolder()) {
						threads[i] = new Thread(new FileDownloader(iNode));
						threads[i].start();
					}
				}
				for (Thread thread : threads) {
					if (thread != null) {
						try {
							thread.join();
						} catch (InterruptedException e) {
							logger.error("Download thread interrupted", e);
						}
					}
				}
			}
		}
	}

	private void refreshFolderHashFiles() {
		files = new File(GlobalConf.HASH_FOLDER).listFiles();
	}
}
